﻿using NHibernate;
using NHibernate.Proxy;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Lhz.Data.Linq.Nh
{
    public static class NhExpansion
    {
        public static T LoadByPathsAndClearProxyAndRestoreProxyData<T>(this T entity, string[] paths = null) where T : class
        {
            if (paths != null)
            {
                entity.LoadProxy(paths);
                entity.ClearProxy();
            }
            return entity.RestoreProxyData();
        }

        public static T RestoreProxyData<T>(this T entity, Dictionary<object, object> items = null) where T : class
        {
            if (entity != null)
            {
                if (items == null)
                {
                    items = new Dictionary<object, object>();
                }
                else
                {
                    if (items.ContainsKey(entity))
                    {
                        return (T)items[entity];
                    }
                }
                var itemType = entity.GetType();
                if (typeof(IEnumerable).IsAssignableFrom(itemType))
                {
                    foreach (var item in entity as IEnumerable)
                    {
                        item.RestoreProxyData(items);
                    }
                }
                else
                {
                    if (typeof(INHibernateProxy).IsAssignableFrom(itemType))
                    {
                        T realEntity = null;
                        var initializer = (entity as INHibernateProxy).HibernateLazyInitializer;
                        if (initializer.IsUninitialized)
                        {
                            initializer.Initialize();
                        }
                        realEntity = (T)initializer.GetImplementation();
                        items.Add(entity, realEntity);
                        entity = realEntity;
                        itemType = realEntity.GetType();
                        if (entity == null)
                        {
                            return entity;
                        }
                    }
                    else
                    {
                        items.Add(entity, entity);
                    }
                    foreach (var pi in itemType.GetProperties())
                    {
                        var pt = pi.PropertyType;
                        if (typeof(IEnumerable).IsAssignableFrom(pt))
                        {
                            var cd = itemType.BuildCheckDelegate(pi.Name);
                            var isInitialized = (bool)cd.DynamicInvoke(entity);
                            if (!isInitialized)
                            {
                                var id = itemType.BuildInitializeDelegate(pi.Name);
                                id.DynamicInvoke(entity);
                            }
                            var pv = pi.GetValue(entity);
                            if (pv != null)
                            {
                                foreach (var item in pv as IEnumerable)
                                {
                                    item.RestoreProxyData(items);
                                }
                            }
                        }
                        else
                        {
                            var pv = pi.GetValue(entity);
                            if (pv != null)
                            {
                                if (pt.IsClass && !pt.Assembly.GlobalAssemblyCache)
                                {
                                    var realPv = pv.RestoreProxyData(items);
                                    if (!pv.Equals(realPv))
                                    {
                                        pi.SetValue(entity, pv.RestoreProxyData(items));
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return entity;
        }

        public static void ClearProxy(this object entity, List<object> items = null)
        {
            if (entity != null)
            {
                if (items == null)
                {
                    items = new List<object>();
                }
                else
                {
                    if (items.Any(r => r.Equals(entity)))
                    {
                        return;
                    }
                }
                var itemType = entity.GetType();
                if (typeof(IEnumerable).IsAssignableFrom(itemType))
                {
                    foreach (var item in entity as IEnumerable)
                    {
                        item.ClearProxy(items);
                    }
                }
                else
                {
                    items.Add(entity);
                    foreach (var pi in itemType.GetProperties())
                    {
                        var pt = pi.PropertyType;
                        if (pt.IsClass || (pt.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pt)))
                        {
                            var cd = itemType.BuildCheckDelegate(pi.Name);
                            var isInitialized = (bool)cd.DynamicInvoke(entity);
                            if (!isInitialized)
                            {
                                pi.SetValue(entity, null);
                            }
                            var pv = pi.GetValue(entity);
                            if (pv != null)
                            {
                                if (pt.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pt))
                                {
                                    foreach (var item in pv as IEnumerable)
                                    {
                                        item.ClearProxy(items);
                                    }
                                }
                                else if (pt.IsClass && !pt.Assembly.GlobalAssemblyCache)
                                {
                                    pv.ClearProxy(items);
                                }
                            }
                        }
                    }
                }
            }
        }

        private static Delegate BuildCheckDelegate(this Type entityType, string propertyName)
        {
            var p = Expression.Parameter(entityType);
            var pa = Expression.Property(p, entityType.GetProperty(propertyName));
            var mce = Expression.Call(typeof(NHibernateUtil).GetMethod("IsInitialized"), pa);
            return Expression.Lambda(mce, p).Compile();
        }

        private static Delegate BuildInitializeDelegate(this Type entityType, string propertyName)
        {
            var p = Expression.Parameter(entityType);
            var pa = Expression.Property(p, entityType.GetProperty(propertyName));
            var mce = Expression.Call(typeof(NHibernateUtil).GetMethod("Initialize"), pa);
            return Expression.Lambda(mce, p).Compile();
        }

        public static readonly string Separator = ".";
        public static readonly string Recursion = "^";

        public static void LoadProxy(this object entity, string[] paths = null)
        {
            if (entity != null && paths != null)
            {
                foreach (var path in paths)
                {
                    entity.LoadPropertyByPath(path);
                }
            }
        }

        private static object LoadPropertyByPath(this object entity, string path)
        {
            if (entity != null && !string.IsNullOrWhiteSpace(path))
            {
                var type = entity.GetType();
                if (typeof(IEnumerable).IsAssignableFrom(type))
                {
                    foreach (var item in entity as IEnumerable)
                    {
                        item.LoadPropertyByPath(path);
                    }
                }
                else
                {
                    var propertyNames = path.Split(new string[] { Separator }, StringSplitOptions.RemoveEmptyEntries);
                    entity.LoadPropertyByPropertyNames(propertyNames);
                }
            }
            return entity;
        }

        private static void LoadPropertyByPropertyNames(this object entity, IEnumerable<string> propertyNames)
        {
            if (propertyNames.Count() == 0)
            {
                return;
            }
            var value = entity;
            var propertyName = propertyNames.First();
            if (propertyName.EndsWith(Recursion))
            {
                //递归属性处理。
            }
            else
            {
                var type = value.GetType();
                var pi = type.GetProperty(propertyName);
                if (pi == null)
                {
                    throw new ArgumentException("类型【" + type.FullName + "】中不包含指定的属性【" + propertyName + "】属性。");
                }
                var cd = type.BuildCheckDelegate(pi.Name);
                var isInitialized = (bool)cd.DynamicInvoke(value);
                if (!isInitialized)
                {
                    var id = type.BuildInitializeDelegate(pi.Name);
                    id.DynamicInvoke(value);
                }
                value = pi.GetValue(value);
                if (value == null)
                {
                    return;
                }
                var propertyNamesWithoutFirst = propertyNames.Skip(1).ToList();
                var pt = pi.PropertyType;
                if (typeof(IEnumerable).IsAssignableFrom(pt))
                {
                    foreach (var item in value as IEnumerable)
                    {
                        item.LoadPropertyByPropertyNames(propertyNamesWithoutFirst);
                    }
                }
                else
                {
                    value.LoadPropertyByPropertyNames(propertyNamesWithoutFirst);
                }
            }
        }
    }
}
